/*******************************************************************************
.               (c) Copyright 2009 Sijo Mathew

.   File       : HighPerformanceTimer.h
.   Filename   : HighPerformanceTimer.h
.   File base  : HighPerformanceTimer
.   File ext   : h
.   Purpose    : To measure the accurate time in different platforms

Author         Date                    Description
--------------------------------------------------------------------------------
Sijo Mathew   8  Jul 2009 14:30         Created
Sijo Mathew   12 Apr 2011 13:50         Added print Time Function
Sijo Mathew   11 Jul 2011 11:24         Start Timer is added in Constructor itself.
                                        Now user can create object and directly
                                        call the PrintTime() or StopTimer()
Sijo Mathew   13 Aug 2011 14:22         Made as platform independent
Sijo Mathew   2 Sept 2013  11:50        Added Sleep to the File
Sijo Mathew   07 Mar 2014  13:34        Added Strings as arguments to constructor

*******************************************************************************/

#ifndef _HIGH_PERFORMANCE_TIMER_H_
#define _HIGH_PERFORMANCE_TIMER_H_

#if defined(linux) || defined(__linux) || defined(__linux__) || defined(__unix__) || defined(unix) || defined(__unix)
#define __PLATFORM_UNIX__
#include <iostream>
using namespace std;
#include <sys/time.h>
#define STOPWATCH timeval
#define stringcomapre strcmp
#elif defined (_WIN32) || defined(__WIN32__) || defined(WIN32) || defined(__WINDOWS__)
#define __PLATFORM_WINDOWS__
#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN 1
#endif
#define NOMINMAX
#include <Windows.h>
#include <iostream>
using namespace std;
#define stringcomapre _stricmp
#define STOPWATCH LARGE_INTEGER
#else
#define __PLATFORM_OTHER__
#include <ctime>
#include <iostream.h>
#define STOPWATCH clock_t
#define stringcomapre strcmp
#endif

#pragma pack(1)

class CPrecisionTimer
{
private:
    STOPWATCH   m_liStart;      // Variable for holding the start tick count
    STOPWATCH   m_liStop;       // Variable for holding the stop tick count

    double      m_dbResolution; // Resolution. By default it is in Seconds

#ifdef __PLATFORM_WINDOWS__
    DWORD       m_dwTimerMask;
    double      m_dbFrequency;  // For finding the system Frequency

    // For finding system the Frequency
    LONGLONG GetFrequency(void)
    {
        STOPWATCH liProcessFrequency;

        if(!::QueryPerformanceFrequency(&liProcessFrequency))
            return 0;

        return liProcessFrequency.QuadPart;
    }
#endif

public:
    CPrecisionTimer(void)
        : m_dbResolution (1)
    {
        StartTimer();
    }

    CPrecisionTimer(double dbResolution)
        : m_dbResolution (dbResolution)
    {
        StartTimer();
    }

    CPrecisionTimer(char* szTimeSlot)
    {
        if (0 == stringcomapre(szTimeSlot, "ms"))
        {
            m_dbResolution = 1000;
        }
        else if (0 == stringcomapre(szTimeSlot, "us"))
        {
            m_dbResolution = 1000000;
        }
        else if (0 == stringcomapre(szTimeSlot, "ns"))
        {
            m_dbResolution = 1000000000;
        }
        /*else if (0 == stringcomapre(szTimeSlot, "ps"))
        {
            m_dbResolution = 1000000000000;
        }*/
        else
        {
            m_dbResolution = 1.0;
        }
        StartTimer();
    }

    void SetResolutionTime(const double& dbResolution)
    {
        m_dbResolution = dbResolution;
    }

    ~CPrecisionTimer(void)
    {
        StopTimer();
#ifdef __PLATFORM_WINDOWS__
        m_dbFrequency = 0;
#endif
    }

    // For Starting the Timer
    bool StartTimer(void)
    {
#ifdef __PLATFORM_UNIX__
        gettimeofday(&m_liStart, NULL);
#endif
#ifdef __PLATFORM_WINDOWS__

        m_dwTimerMask = 1;

        // Get the current process core mask
        //DWORD procMask = 0;
        //DWORD sysMask  = 0;
        DWORD_PTR procMask = NULL;
        DWORD_PTR sysMask  = NULL;

#if _MSC_VER >= 1400 && defined (_M_X64)
        if(!::GetProcessAffinityMask(GetCurrentProcess(), (PDWORD_PTR)&procMask, (PDWORD_PTR)&sysMask))
            return false;
#else
        if(!::GetProcessAffinityMask(GetCurrentProcess(), &procMask, &sysMask))
            return false;
#endif

        // If procMask is 0, consider there is only one core available
        // (using 0 as procMask will cause an infinite loop below)
        if (procMask == 0)
            procMask = 1;

        // Find the lowest core that this process uses
        while( ( m_dwTimerMask & procMask ) == 0 )
        {
            m_dwTimerMask <<= 1;
        }

        if (0 == GetFrequency())
            return false;

        m_dbFrequency      = m_dbResolution / GetFrequency();
        // Set affinity to the first core
        DWORD_PTR dwpMask  = ::SetThreadAffinityMask(::GetCurrentThread(), m_dwTimerMask);
        if (0 == dwpMask)
            return false;

        if(!::QueryPerformanceCounter(&m_liStart))
            return false;

        // Reset affinity
        dwpMask = ::SetThreadAffinityMask(::GetCurrentThread(), dwpMask);

        if (0 == dwpMask)
            return false;

#endif
#ifdef __PLATFORM_OTHER__
        m_liStart = clock();
#endif

        return true;
    }

    // To Stop the Timer and get the time
    double StopTimer(void)
    {
#ifdef __PLATFORM_UNIX__
        gettimeofday(&m_liStop, NULL);

        // calculate time in microseconds
        /* double dbStartTime = m_liStart.tv_sec * 1000000 + (m_liStart.tv_usec);
        double dbEndTime   = m_liStop.tv_sec * 1000000  + (m_liStop.tv_usec);
        double dbTotalTime = dbEndTime - dbStartTime;
        return (dbTotalTime / 1000000) * m_dbResolution;*/

        return (((m_liStop.tv_sec  - m_liStart.tv_sec) +
            ((m_liStop.tv_usec - m_liStart.tv_usec) / 1000000.0))
            * m_dbResolution);

        // return ((m_liStop.tv_sec - m_liStart.tv_sec) + ((m_liStop.tv_usec - m_liStart.tv_usec) / 1000000.0)); // time in milli seconds
#endif
#ifdef __PLATFORM_WINDOWS__
        DWORD_PTR dwpMask = ::SetThreadAffinityMask(::GetCurrentThread(), m_dwTimerMask);
        if (0 == dwpMask)
            return -1;

        if(::QueryPerformanceCounter(&m_liStop))
        {
            dwpMask = ::SetThreadAffinityMask(::GetCurrentThread(), dwpMask);

            if (0 == dwpMask)
                return -1;

            return ((m_liStop.QuadPart - m_liStart.QuadPart) * m_dbFrequency);
        }

        return -1;
#endif
#ifdef __PLATFORM_OTHER__
        m_liStop = clock();
        return static_cast<double>(((double)(m_liStop - m_liStart) /
            (double)CLOCKS_PER_SEC) * m_dbResolution);
#endif
    }

    double PrintTime(void)
    {
        double dbTime = StopTimer();

        switch ((int)m_dbResolution)
        {
        case 1:
            cout<<"Time taken = "<<dbTime<<" s\n";
            break;

        case 1000:
            cout<<"Time taken = "<<dbTime<<" ms\n";
            break;

        case 1000000:
            cout<<"Time taken = "<<dbTime<<" us\n";
            break;

        case 1000000000:
            cout<<"Time taken = "<<dbTime<<" ns\n";
            break;

        /*case 1000000000000:
            cout<< "Time taken = "<<dbTime<<" ps\n";
            break;*/

        default:
            cout<<"Time taken = "<<dbTime<<"\n";
        }

        return dbTime;
    }

    void PrintTime(char* szString)
    {
        double dbTime = StopTimer();

        switch ((int)m_dbResolution)
        {
        case 1:
            cout<< szString <<"Time taken = "<<dbTime<<" s\n";
            break;

        case 1000:
            cout<< szString <<"Time taken = "<<dbTime<<" ms\n";
            break;

        case 1000000:
            cout<< szString <<"Time taken = "<<dbTime<<" us\n";
            break;

        case 1000000000:
            cout<< szString <<"Time taken = "<<dbTime<<" ns\n";
            break;

        /*case 1000000000000:
            cout<< szString <<"Time taken = "<<dbTime<<" ps\n";
            break;*/

        default:
            cout<< szString <<"Time taken = "<<dbTime<<"\n";
        }
    }

    double PrintTime(char* szPrefix, char* szPostfix)
    {
        double dbTime = StopTimer();
        cout<<szPrefix<<" "<<dbTime<<" "<<szPostfix<<"\n";
        return dbTime;
    }

    void uSleep(int waitTime)
    {
        __int64 time1 = 0, time2 = 0/*, freq = 0*/;

        QueryPerformanceCounter((LARGE_INTEGER *) &time1);
        //QueryPerformanceFrequency((LARGE_INTEGER *)&freq);

        do
        {
            QueryPerformanceCounter((LARGE_INTEGER *) &time2);
        } while((time2-time1) < waitTime);
    }
};

#endif  //_HIGH_PERFORMANCE_TIMER_H

/* Usage:-
CPrecisionTimer TimerObjSeconds; // By default it will give time in Seconds
// if timing is to be obtained in milliseconds give argument as 1000 while creating the object.
CPrecisionTimer TimerObjMilliSeconds(1000);
CPrecisionTimer TimerObjMicroSeconds(1000000);
CPrecisionTimer TimerObjMicroSeconds("ms"); // will find the time in milliseconds. Default is in seconds
// other strings are
// Milliseconds - "ms" 
// Microseconds - "us"
// Nanoseconds  - "ns"

TimerObjSeconds.StartTimer();
//place where timing is to be taken
double TimeSeconds = TimerObjSeconds.StopTimer(); or
TimerObjSeconds.PrintTime();

New method :
CPrecisionTimer TimerObj(); // give appropriate range
TimerObjSeconds.PrintTime();

*/
